#include "deds/atom.h"
#include "deds/alloc.h"
#include <string.h>
#include <assert.h>

#ifdef MXDEBUG
#   define MXMODULE_ALLOC
#endif

#define MX__ATOM_WANTDELETE  0x01
#define MX__ATOM_ALLOCATED   0x02

/** !Construct an MX_ATOM
This internal function constructs an MX_ATOM and registers a
destructor to call when the object gets deleted. */
void *mx__atom(MX_ATOM * atom, MX__ATOM_DESTRUCT destruct, size_t datasize)
{
	unsigned allocated = false;

		 /** If insufficient object size is suggested then the actually allocated
             memory is increased to safely contain an MX_ATOM.  Passing 0 for datasize
             is safe.  */
	if (datasize < sizeof(MX_ATOM))
		datasize = sizeof(MX_ATOM);

		 /** If no existing MX_ATOM memory is given then memory will be allocated. */
	if (!atom) {
		atom = (MX_ATOM *) mx_malloc(datasize);
		allocated = true;
	}

	memset(atom, 0, datasize);
	atom->_destruct = destruct;

	if (allocated)
		atom->_flags |= MX__ATOM_ALLOCATED;

#ifdef MX_ATOMNAME
	atom->name = "unnamed";
#endif
		 /** The address of the newly constructed MX_ATOM is returned. */
	return atom;
}

static void mx__atom_destruct(MX_ATOM * atom)
{
	unsigned allocated = (atom->_flags & MX__ATOM_ALLOCATED) ? true : false;

	if (atom->_destruct) {
		MX__ATOM_DESTRUCT destruct = atom->_destruct;

		atom->_destruct = 0;

		destruct(atom);
	}

	memset(atom, 0, sizeof(*atom));

	if (allocated)
		mx_free(atom);
}

unsigned mx__atom_delete(MX_ATOM * atom)
{
	if (!atom)
		return false;

	assert(atom);

	/* Mark the atom for destruction */
	if (atom->_flags & MX__ATOM_WANTDELETE)
		return false;
	atom->_flags |= MX__ATOM_WANTDELETE;

	/* Dont destruct if we still have references */
	if (atom->_lastlock)
		return false;

	mx__atom_destruct(atom);
	return true;
}

void mx__lock(MX_LOCK * lock, MX_ATOM * atom)
{
	assert(lock);
	assert(atom);

	if (!atom) {
		memset(lock, 0, sizeof(*lock));
		return;
	}

	lock->_atom = atom;
	lock->_next = 0;
	lock->_prev = atom->_lastlock;

	if (atom->_lastlock)
		atom->_lastlock->_next = lock;

	atom->_lastlock = lock;
}

unsigned mx_unlock(MX_LOCK * lock)
{
	unsigned didit = false;
	MX_ATOM *atom = lock->_atom;

	if (!atom)
		return false;

	assert(lock);
	assert(atom);

	/* Remove the lock from the list, and make sure the lock cant be used to
	   unlock again */
	if (lock->_prev)
		lock->_prev->_next = lock->_next;
	if (lock->_next)
		lock->_next->_prev = lock->_prev;
	if (atom->_lastlock == lock)
		atom->_lastlock = lock->_prev;
	lock->_atom = 0;
	lock->_prev = 0;
	lock->_next = 0;

	if ((atom->_lastlock == 0) && (atom->_flags & MX__ATOM_WANTDELETE)) {
		mx__atom_destruct(atom);
		didit = true;
	}

	memset(lock, 0, sizeof(*lock));

	return didit;
}

unsigned mx_lastlock(const MX_LOCK * lock)
{
	MX_ATOM *atom = lock->_atom;

	if (!atom)
		return false;

	assert(lock);
	assert(atom);

	if ((atom->_flags & MX__ATOM_WANTDELETE) && (atom->_lastlock == lock)
		&& (lock->_prev == 0) && (lock->_next == 0))
		return true;

	return false;
}

/*
   DEDS
   Data structures and utility functions for C
   Copyright (C) 2006 Douglas Eleveld

   This program is free software; you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation; either version 2 of the License, or
   (at your option) any later version.

   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with this program; if not, write to the Free Software
   Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 */
